__author__ = 'Muscle'
from mui.muirectangle import Rectangle
from mui.muievent import MUIEventDispatcher
from mui.CExt.CW32Window import *
import mui.CExt.muiDX as muiDX
import mui.muirender
import mui.InternalUse.muiconfig as config
import color
import traceback
import mui.InternalUse.muicommon as muicommon
from mui.InternalUse.muicommon import enum
from muikeys import keyStr
import random
import re

_MOUSE_MSGTYPE_ = enum(motion = 0, press = 1, release = 2, start_drag = 3, end_drag = 4,  dragging = 5, dblclick = 6, scroll = 7, enter =8, leave = 9)
_KEY_MSGTYPE_ = enum(press = 0, release = 1)
_TEXT_MSGTYPE_ = enum(text = 0, motion = 1, select = 2)

_MOUSE_BUTTON_= enum(LEFT = 1 , MIDDLE = 2 , RIGHT = 4)

def clamp(min_v, v, max_v):
    return max( min_v, min(v, max_v) )

class DragDropBase:
    pass

class IWindow(object):
    def getHwnd(self):
        return self.hwnd


def recursiveWalkComponent(node, func):
    func(node)
    map(lambda c: recursiveWalkComponent(c, func), node.children)

class CommandBase:
    def __init__(self, **kwargs):
        self.command = kwargs.get('command', '')
        self.bind('Mouse Up', self.onCmd, postevent = False, noarg = True)

    def onCmd(self):
        if self.command:
            self.dispatch_cmd(self.command, self)

class DragDropBase:
    """ DragDropBase
    Event:
        - Set Drop Files
        - Drop Files
        - Drag Enter
        - Drag Leave
    """
    dragOver = None

    def __init__(self):
        assert isinstance(self, MUIComponent)
        self.isDragOver = False

        # property
        self.dropMode = 'ole'               # dropMode == 'mui' for dragdrop python object
                                            # dropMode == 'ole' for dragdrop ole object
        self.canAcceptDropFiles = False     # can accept drop files?
        self.canDragFiles = False           # can drag files out?
        self.__isFirstMouseDown = False
        self.__mouseClickPt = 0, 0
        self.bind('Mouse Down', self.__onMouseDown)
        self.bind('Mouse Up', self.__onMouseUp)
        self.bind('Mouse Move', self.__onMouseMove)

    def __onMouseDown(self, comp, x, y, buttons, modifiers):
        self.__isFirstMouseDown = True
        self.__mouseClickPt = x, y

    def __onMouseUp(self, comp, x, y, buttons, modifiers):
        self.__isFirstMouseDown = False

    def __onMouseMove(self, comp, x, y, dx, dy):
        if self.__isFirstMouseDown:
            clickx, clicky = self.__mouseClickPt
            if self.canDragFiles and (MUIComponent.mouseDownIn or MUIComponent.mouseRDownIn):
                self.onPrepareStartDrag(clickx, clicky)
            self.__isFirstMouseDown = False

    def onPrepareStartDrag(self, x, y):
        #print '[component.py] onPrepareStartDrag'
        pnt = self.local2global(x, y)
        self.window.prepareDragDrop(pnt[0], pnt[1], self)

    def onQueryOleDropData(self):
        """
        override to return your ole drop data
        """
        return None

    def onQueryMUIDropData(self):
        """
        override to return your koan drop data
        """
        return None

    def onQueryDrop(self, _x, _y):
        """
        drop query (for mui mode)
        """
        if self.canAcceptDropFiles and \
            (self.window.oleDropData != None or self.window.muiDropData != None):
            self.window.SetCursor('drop')
            #print '[component.py] onQueryDrop', self.__class__
            return True
        return False

    def onQueryDropEffect(self, _x, _y):
        """
        pass query drop effect (for ole mode)
        @rtype: 0: None, 1:Copy, 2:Move
        """
        # for OLE drop query
        return 1

    def onDropFiles(self, x, y, files, muiClipInfo):
        if self.canAcceptDropFiles:
            return self.invoke('Drop Files', x, y, files, muiClipInfo)
        return None

    def passQueryDrop(self, x, y, state, *arg):
        """
        pass query drop message to children, and then process the query message

        @rtype: True if mouse over myself or one of my children
        """
        if not self.visible:
            return False

        for c in self.children[::-1]:
            if c.visible and not c.blind:

                # In some situations (scroll in StackPanel), local coordinate has been translated by scrolling or others.
                # And hittest need the original rect

                ptx, pty = c.parent2local(x, y)
                _x, _y = MUIComponent.parent2local(c, x, y)
                if c.hitTest(_x, _y):
                #if c.hitTest(x, y):
                    ret = c.passQueryDrop(ptx, pty, state, *arg)
                    if ret:
                        return ret
        return self.handleQueryDrop(x, y, state, *arg)

    def handleQueryDrop(self, x, y, state, *arg):
        dragOver = self.hitTest(x, y)

        if dragOver and self.canAcceptDropFiles:
            # in drag state, we still need to maintain mouse over state
            if MUIComponent.mouseOver != self:
                if MUIComponent.mouseOver:
                    MUIComponent.mouseOver.isMouseOver = False
                    MUIComponent.mouseOver.invoke('Mouse Leave')
                    #print 'MouseLeave drag', Component.mouseOver
                MUIComponent.mouseOver = self
                self.isMouseOver = True
                self.invoke('Mouse Enter')
                #print 'MouseEnter drag', Component.mouseOver

            if DragDropBase.dragOver != self:
                if DragDropBase.dragOver:
                    DragDropBase.dragOver.isDragOver = False
                    DragDropBase.dragOver.invoke('Drag Leave')
                DragDropBase.dragOver = self
                self.isDragOver = True
                self.invoke('Drag Enter')

            if state == 'QUERY':
                return self.onQueryDrop(x, y)
            elif state == 'QUERYEX':
                return self.onQueryDropEffect(x, y)
            elif state == 'DROP':
                oleDropData = arg[0]
                muiDropData = arg[1]
                ret = self.onDropFiles(x, y, oleDropData, muiDropData)
                DragDropBase.dragOver = None
                self.isDragOver = False
                return ret and any(ret)
            return True

        return False


class MUIComponent(Rectangle, MUIEventDispatcher, DragDropBase):
        mouseOver = None
        mouseDownIn = None
        mouseRDownIn = None
        mousePreDown = None
        mouseCapture = None
        tabStopObj = None
        mouseDragSrc = None
        mouseDragPassData = {}
        
        count = 0

        def __init__(self, parent=None,  **kwargv):
            MUIComponent.count += 1
            Rectangle.__init__(self)
            MUIEventDispatcher.__init__(self)
            DragDropBase.__init__(self)
            ###########apparent related
            self.visible = True
            self.bgColor = (0,0,0,0)
            self.background = ''
            self.opacity = 1.0
            self.passthrough = False
            self.puncture = False      # I didn't implement it yet
            self.bClip = kwargv.get('bClip', False)
            self.isMouseOver = False
            ###########Parent tree related#########3
            self._parent = None
            self._children = []
            self._root = None
            self.focusChild = None
            self.parent = parent
            self.name = ''
            self.tabStop = 1000  # decide the tab focus order
            ###########MUI component property#########
            self.enabled = True
            self.blind = False
            self.dead = False
            self.dirty = True
            self._preDirtyRect = None # need to remember last time rectangle, so the we can get the correct dirty area after 'position change'
            self._dirtyRect = None # an temporal list, will be filled in calculateDirtyRect_step1, calculateDirtyRect_step2
            ###########layout related###############
            maxWidth = kwargv.get('maxWidth', None)
            maxHeight = kwargv.get('maxHeight', None)
            minWidth = kwargv.get('minWidth', None)
            minHeight = kwargv.get('minHeight', None)

            if (maxWidth):
                self.maxWidth = maxWidth
            if (maxHeight):
                self.maxHeight = maxHeight
            if (minWidth):
                self.minWidth = minWidth
            if (minHeight):
                self.minHeight = minHeight
                
            ###########Other initial################
            self.setDefault(**kwargv)

            self.changeEvent('left', self._postPosition, postevent = False, sync=False)
            self.changeEvent('top', self._postPosition, postevent = False, sync=False)
            self.changeEvent('width', self._postSize, postevent = False, sync=False)
            self.changeEvent('height', self._postSize, postevent = False, sync=False)
            self.changeEvent('enabled', self._onEnableChanged, postevent = False, sync=False)
            self.changeEvent('puncture', self._onPunctureChange, postevent = False, sync=False)
            self.changeEvent('visible', self._onVisibleChange, postevent = False, sync=False)
            self.changeEvent('focusChild', self.dispatch_event, actArgs = ('Focus Child Change',))

            self.autoDirty(
                [
                    'visible',
                    'opacity',
                    'background',
                    'bgColor',
                    'focus'
                ], [
                    'Size Change',
                    'Position Change',
                    'Visible Change',
                    'Child Add',
                    'Child Remove',
                    'Focus Change'
                ]
            )


        def __del__(self):
            super(MUIComponent, self).__del__()
            Component.count -= 1
        
        def autoDirty(self, attributes, events=None):
            if events is None:
                events = []
            for attr in attributes:
                self.changeEvent(attr, self.setDirty, actArgs = (True,), sync = False)
            for evt in events:
                self.bind(evt, self.setDirty, True,  noarg = True )

        def setDirty(self, bDirty, **kwargs):
            self.dirty = bDirty
            if (bDirty ):
                for c in self._children:
                    c.setDirty(bDirty)
          

        def setDefault(self, **kwargs):
            """ update member data by kwargs """
            try:
                for name, value in kwargs.items():
                    if hasattr(self, name):
                        object.__setattr__(self, name, value)
                        kwargs.pop(name)
                return kwargs
            except Exception, e:
                traceback.print_exc()
                return None

        def _getDirtyRect(self):
            return self.globalRect

        dirtyRect = property(_getDirtyRect)

        def isVisible(self):
            v = self
            while v:
                if not v.visible:
                    return False
                v = v.parent
            return True

        def getChildren(self):
            return self._children
        children = property(getChildren)

        def getTabChildren(self):
            children = []
            for c in self.children:
                if c.visible and c.enabled and not c.blind:
                    if c.tabStop:
                        children.append(c)
                    children += c.tabChildren
            if children:
                children.sort(lambda x, y: cmp(x.tabStop, y.tabStop))
            return children
        tabChildren = property(getTabChildren)

        def getRoot(self):
            if self._root != None:
                return self._root
            t = self
            while t.parent:
                t = t.parent
            self._root = t
            return t

        root = property(getRoot)

        def getWindow(self):
            root = self.root
            assert bool(root)
            return root if root is not self and isinstance(root, IWindow) else None
        window = property(getWindow)


        def show(self):
            self.visible = True

        def hide(self):
            self.visible = False

        def showHide(self):
            self.visible = not self.visible

        def getParent(self):
            if self._parent:
                return self._parent
            return None

        def isAscendant(self, obj):
            ''' check if obj is a ascendant
            '''
            parent = self.parent
            while parent:
                if obj is parent:
                    return True
                parent = parent.parent
            return False

        def isDescendant(self, obj):
            return obj and obj.isAscendant(self)

        def setParent(self, newparent, **kwargs):
            curparent = self.getParent()
            if newparent is curparent:
                return  # no change

            if curparent:
                curparent._removeChild(self)
            if newparent:
                self._parent = newparent
                newparent._addChild(self, **kwargs)
            else:
                self._parent = None
            recursiveWalkComponent( self, lambda x: setattr(x, '_root', None) ) #set _root as None, so the getRoot function will reassign them.
            self.dispatch_event('Parent Change', self, newparent)

        parent = property(getParent, setParent)

        def close(self):
            if MUIComponent.tabStopObj is self:
               MUIComponent.tabStopObj = None
            if MUIComponent.mouseCapture is self:
               MUIComponent.mouseCapture = None
            if MUIComponent.mouseOver is self:
               MUIComponent.mouseOver = None
            if MUIComponent.mouseDownIn is self:
               MUIComponent.mouseDownIn = None
            if MUIComponent.mouseRDownIn is self:
               MUIComponent.mouseRDownIn = None
            if self.dead:
               print 'second close Warring:', str(self)
            self.free()
            self.clear()

        def free(self):
            """
            break the connection between parent and children
            """
            if self.dead:
                print 'Component has been dead', self
                return

            self.dead = True
            if self.parent:
                 self.parent.dirty = True
            self.dispatch_event('Close', self)
            
            self.freeChildren()
            self.parent = None

            # change some value to dead state
            # set after EventManager.clear() to prevent trigger change event
            self.visible = False

        def freeChildren(self):
            self.dirty = True
            self.focusChild = None

            while self._children:
                c = self._children[-1]
                if c:
                    c.close()

            self._children = []


        def setFocus(self, hint = '', **kwargs):

            if not self.enabled or not self.isVisible():
               return
            force = kwargs.get('force', False)

            if not force and self.isFocused():
               if self.focusChild:
                   self.focusChild.setFocus(hint)
               return
            # collect pre-focus
            window = self.window
            top = window.focusChild

            # save all previous focus object
            preFocus = set()
            if top:
               preFocus.add(top)
               while top.focusChild:# and top.focusChild.enabled and top.focusChild.visible:
                   top = top.focusChild
                   preFocus.add(top)

            # focus on self, not on child
            if force:
               self.focusChild = None

            # set focus
            ch = self
            own = self.parent
            while own:
               own.focusChild = ch
               ch = own
               own = own.parent

            # collect now-focus
            top = window
            if top:
                curFocus = set([top])
                while top.focusChild:# and top.focusChild.enabled and top.focusChild.visible:
                   top = top.focusChild
                   curFocus.add(top)

                for e in curFocus:
                   if e not in preFocus:
                       e.dispatch_event('Focus Change')

                for e in preFocus:
                   if e not in curFocus:
                       e.dispatch_event('Focus Change')

            # update tabStopObj
            MUIComponent.tabStopObj = None
            tso = self
            while tso:
               if tso.tabStop:
                    MUIComponent.tabStopObj = tso
                    break
               tso = tso.parent


        def isFocused(self):
            if not self.enabled or not self.isVisible():
                return False
            if self.parent:
                return self.parent.focusChild == self and self.parent.isFocused()
            return True
        focus = property(isFocused)

        def initTabStop(self):
            """ set first tab object as current tab stop object
            """
            children = self.tabChildren
            if children:
                children[0].setFocus()
            else:
                MUIComponent.tabStopObj = None


        def _removeChild(self, c):
            if c.parent == self:
                c._parent = None

            if self.focusChild == c:
                self.focusChild = None

            if c in self._children:
                self.dispatch_event('Child Remove', self, c)
                self._children.remove(c)

            if self.focusChild is None and self.isFocused():
                self.guessFocusChild()


        def addChild(self, c, **kwargs):
            c.setParent(self, **kwargs)

        def _addChild(self, c, **kwargs):
            if c in self._children:
                raise TypeError
            if kwargs.get('front', False):
                self._children.insert(0, c)
            else:
                self._children.append(c)
            self.dispatch_event('Child Add', self, c)

        def guessFocusChild(self, hint = ''):
            """
            set focus to one child (guess?)
            @rtype: None
            """
            if self.dead:
                return

            if self.focusChild:
                if not self.focusChild.enabled or not self.focusChild.visible:
                    top = self.focusChild
                    preFocus = set()
                    preFocus.add(top)
                    while top.focusChild:# and top.focusChild.enabled and top.focusChild.visible:
                        top = top.focusChild
                        preFocus.add(top)

                    self.focusChild = None
                    for e in preFocus:
                        e.invoke('Focus Change')
                else:
                    return

            for e in self.children:
                if e.enabled and e.visible:
                    e.setFocus(hint)
                    break

        def on_draw(self, renderer):
            if self.background:
                renderer.DrawBitmap(self.background, (0, 0, self.width, self.height), muiDX.Fill, self.opacity)
            else:
                drawColor = (self.bgColor[0],self.bgColor[1], self.bgColor[2], self.bgColor[3]*self.opacity)
                renderer.FillColorRectangle((0, 0, self.width, self.height), drawColor)
            if (MUIComponent.tabStopObj == self):
                self.drawFocusEffect(renderer)

            if (config.g_bShowDirty):
                renderer.DrawColorRectangle((3, 3, self.width-3, self.height-3), (1,random.random(), random.random(),1), 1 )

        def drawFocusEffect(self, renderer):
            renderer.FillColorRectangle((3, 3, self.width-3, self.height-3), (1,0,0,0.7) )

        def drawBorder(self, renderer, thick, clr):
            try:
                renderer.DrawColorRectangle((thick*0.5, thick*0.5, self.width-thick*0.5, self.height-thick*0.5), clr, thick )
            except Exception, e:
                traceback.print_exc()
                return None

        def beginPassDraw(self, renderer):
            renderer.PushMatrix()
            renderer.Translate(self.left, self.top, 0.0)
            if self.bClip:
                renderer.PushAxisAlignedClip((0, 0, self.width, self.height))

        def endPassDraw(self, renderer):
            if self.bClip:
                renderer.PopAxisAlignedClip()
            renderer.PopMatrix()

        def passDraw(self, renderer):
            if self.visible:
                self.beginPassDraw(renderer)
                if (config.g_bUseDirtyRegion):
                    if self.dirty:
                        self.on_draw(renderer)
                    else:
                         if (hasattr(self, '_dirtyRect') and self._dirtyRect):
                             for dRect in self._dirtyRect:
                                localLT = self.global2local(dRect.rect[0], dRect.rect[1])
                                clipRect = (localLT[0], localLT[1],  localLT[0] + dRect.rect[2], localLT[1] + dRect.rect[3])

                                renderer.PushAxisAlignedClip(clipRect)
                                self.on_draw(renderer)
                                renderer.PopAxisAlignedClip()

                                #_cRect = (clipRect[0] + 4, clipRect[1] + 4, clipRect[2] - 4, clipRect[3] - 4)
                                #renderer.DrawColorRectangle(_cRect, (0.0, 1.0, 0.0, 0.5), 2)
                else:
                    self.on_draw(renderer)

                self.drawChild(renderer)
                self._preDirtyRect = self.globalRect[:]
                self.dirty = False
                self.endPassDraw(renderer)
            else:
                self._preDirtyRect = None

        def drawChild(self, render):
            map(lambda c: c.passDraw(render), self._children)

        def hitTest(self, x, y):
            return (x >= 0 and x < self.width and y >= 0 and y < self.height)

        def passMouseMsg(self, msgType, x, y, **kwargs):
            if MUIComponent.mouseCapture is not self:
                '''
                In some situations (scroll in StackPanel), local coordinate has been translated by scrolling or others.
                And hittest need the original rect
                '''
                if (MUIComponent.mouseCapture != None):
                    __x , __y = self.local2global(x, y)
                    _x, _y = MUIComponent.mouseCapture.global2local(__x, __y)
                    if (MUIComponent.mouseCapture.handleMouseMsg(msgType, _x, _y, **kwargs)):
                        return True
                if not self.hitTest(x, y):
                    if self.bClip:
                        return False
                    else:
                        if self.passMouseMsgToChildren(msgType, x, y, **kwargs):
                            return True
                    return False
                    
                if not self.enabled:
                    return self.handleMouseMsg(msgType, x, y, **kwargs)   # control is disabled, so mouse event should stop here
                if self.passMouseMsgToChildren(msgType, x, y, **kwargs):
                    return True # mouse pass to child
            return self.handleMouseMsg(msgType, x, y, **kwargs)


        def handleMouseMsg(self, msgType, x, y, **kwargs):
            if self.passthrough:
                return False

            #---------------------------------------------
            # check mouse move
            #---------------------------------------------
            if msgType == _MOUSE_MSGTYPE_.motion:
                if MUIComponent.mouseOver != self:
                    if MUIComponent.mouseOver:
                        MUIComponent.mouseOver.isMouseOver = False
                        MUIComponent.mouseOver.dispatch_event('Mouse Leave', MUIComponent.mouseOver, x, y)
                MUIComponent.mouseOver = self
                self.isMouseOver = True
                self.dispatch_event('Mouse Enter', self, x, y)

                dx = kwargs.get('dx')
                dy = kwargs.get('dy')
                self.dispatch_event('Mouse Move', self, x, y, dx, dy)

            #---------------------------------------------
            # disabled control should not fire following events
            #---------------------------------------------
            if not self.enabled:
                return True

            #---------------------------------------------
            # check if double click
            #---------------------------------------------
            if msgType ==  _MOUSE_MSGTYPE_.dblclick:
                buttons = kwargs.get('buttons')
                modifiers = kwargs.get('modifiers')
                if MUIComponent.mousePreDown is self:
                    if buttons == _MOUSE_BUTTON_.LEFT:
                        self.isMouseDown = True
                        self.dispatch_event('Dbl Click', self, x, y, buttons, modifiers)

                    if buttons == _MOUSE_BUTTON_.RIGHT:
                        self.isMouseRDown = True
                        self.dispatch_event('RDbl Click', self, x, y, buttons, modifiers)


            #---------------------------------------------
            # check if mouse down
            #---------------------------------------------
            if msgType ==  _MOUSE_MSGTYPE_.press:
                buttons = kwargs.get('buttons')
                modifiers = kwargs.get('modifiers')
                if buttons == _MOUSE_BUTTON_.LEFT:
                    if MUIComponent.mouseDownIn:
                        _x, _y = self.local2global(x, y)
                        _x, _y = MUIComponent.mouseDownIn.global2local(_x, _y)
                        mouseDownIn = MUIComponent.mouseDownIn
                        MUIComponent.mouseDownIn = None
                        mouseDownIn.dispatch_event('Mouse Up', mouseDownIn, _x, _y, buttons, modifiers)

                    if MUIComponent.mouseDownIn == None:
                        self.isMouseDown = True
                        self.dispatch_event('Mouse Down', self, x, y, buttons, modifiers)

                if buttons == _MOUSE_BUTTON_.RIGHT:
                    if MUIComponent.mouseRDownIn:
                        _x, _y = self.local2global(x, y)
                        _x, _y = MUIComponent.mouseRDownIn.global2local(_x, _y)
                        mouseRDownIn = MUIComponent.mouseRDownIn
                        MUIComponent.mouseRDownIn = None
                        mouseRDownIn.dispatch_event('Mouse RUp', mouseRDownIn, _x, _y, buttons, modifiers)

                    if MUIComponent.mouseRDownIn == None:
                        self.isMouseRDown = True
                        self.dispatch_event('Mouse RDown', self, x, y, buttons, modifiers)

            #---------------------------------------------
            # check if mouse up
            #---------------------------------------------

            if msgType ==  _MOUSE_MSGTYPE_.release:
                buttons = kwargs.get('buttons')
                modifiers = kwargs.get('modifiers')
                if buttons == _MOUSE_BUTTON_.LEFT:
                    mouseDownIn = MUIComponent.mouseDownIn
                    MUIComponent.mouseDownIn = None
                    if mouseDownIn == self:
                        self.dispatch_event('Mouse Up', self, x, y, buttons, modifiers)
                    elif mouseDownIn:
                        _x, _y = self.local2global(x, y)
                        _x, _y = mouseDownIn.global2local(_x, _y)
                        mouseDownIn.dispatch_event('Mouse Up', mouseDownIn,  _x, _y, buttons, modifiers)
                elif buttons == _MOUSE_BUTTON_.RIGHT:
                    mouseRDownIn = MUIComponent.mouseRDownIn
                    MUIComponent.mouseRDownIn = None
                    if mouseRDownIn == self:
                        self.dispatch_event('Mouse RUp', self, x, y, buttons, modifiers)
                    elif mouseRDownIn:
                        _x, _y = self.local2global(x, y)
                        _x, _y = mouseRDownIn.global2local(_x, _y)
                        mouseRDownIn.dispatch_event('Mouse RUp', mouseRDownIn, _x, _y, buttons, modifiers)

            if msgType == _MOUSE_MSGTYPE_.scroll:
                buttons = kwargs.get('buttons')
                modifiers = kwargs.get('modifiers')
                dz = kwargs.get('dz', 0.0)
                mouseOver = MUIComponent.mouseOver
                if mouseOver == self:
                    self.dispatch_event('Mouse Wheel', self, x, y, dz, buttons, modifiers)
                elif mouseOver:
                    _x, _y = self.local2global(x, y)
                    _x, _y = mouseOver.global2local(_x, _y)
                    mouseOver.dispatch_event('Mouse Wheel', mouseOver, _x, _y, dz, buttons, modifiers)

            return True

        def passMouseMsgToChildren(self, msgType, x, y, **kwargs):
            for c in self.children[::-1]:
                if c.visible and not c.blind:
                    _x, _y = c.parent2local(x, y)
                    if c.passMouseMsg(msgType, _x, _y, **kwargs):
                        return True
            return False

        def getIsMouseDown(self):
            return self == MUIComponent.mouseDownIn
        def setIsMouseDown(self, v):
            #if Component.mouseDownIn:
            #    Component.mouseDownIn.dirty = True
            if self.getIsMouseDown() == v:
                return
            if v:
                MUIComponent.mouseDownIn = self
                MUIComponent.mousePreDown = self
            elif MUIComponent.mouseDownIn == self:
               MUIComponent.mouseDownIn = None
        isMouseDown = property(getIsMouseDown, setIsMouseDown)

        def getIsMouseRDown(self):
            return self == MUIComponent.mouseRDownIn

        def setIsMouseRDown(self, v):
            if self.getIsMouseRDown() == v:
                return
            if v:
                MUIComponent.mouseRDownIn = self
                MUIComponent.mousePreDown = self
            elif MUIComponent.mouseRDownIn == self:
                MUIComponent.mouseRDownIn = None
        isMouseRDown = property(getIsMouseRDown, setIsMouseRDown)

        def addAcceleratorKey(self, key, cmd):
            if not hasattr(self, 'keymaps'):
                self.keymaps = {}
            if key not in self.keymaps:
                self.keymaps[key] = []
            self.keymaps[key].append(cmd)

        def passKey(self, msgType, key, symbol, modifiers, **kwargs):
            if (msgType == _KEY_MSGTYPE_.press):
                assert self.enabled and self.visible
                if self.blind and (not (symbol == VK_F4 and modifiers & MOD_ALT)):
                    return True

                if symbol == VK_TAB:
                    children = self.tabChildren
                    if children:
                        try:
                            idx = children.index(MUIComponent.tabStopObj)
                            idx += (1 if modifiers & MOD_SHIFT else -1)
                            idx = idx % len(children)
                        except ValueError:
                            idx = (0 if modifiers & MOD_SHIFT else -1)
                        c = children[idx]
                        c.setFocus(force=True)
                        #print 'tab stop', c
                        return True
                keymaps = kwargs.get('keymaps', {})
                if hasattr(self, 'keymaps') and kwargs.get('updateKeyMap', True):
                    keymaps = keymaps.copy()
                    keymaps.update(self.keymaps)
                ret = False
                if self.focusChild:
                    ret = self.focusChild.passKey(msgType, key, symbol, modifiers, keymaps = keymaps, **kwargs)
                if not ret:
                    if keymaps:
                        if key in keymaps:
                            for evt in keymaps[key]:
                                if (self.dispatch_cmd(evt)):
                                    return True
                    ret = self.onKey(msgType, key, symbol, modifiers)
                return ret
            
            elif (msgType == _KEY_MSGTYPE_.release):
                pass
            return False

        def onKey(self, msgType, key, symbol, modifiers):
            if (msgType == _KEY_MSGTYPE_.press):
                #print 'Key:', symbol, modifiers, self
                if self.dispatch_event('Key', self, msgType, key, symbol, modifiers):
                    return True
            return False

        def passText(self, msgType, **kwargs):
            assert self.enabled and self.visible
            if self.blind:
                return True


            ret = False
            if self.focusChild:
                ret = self.focusChild.passText(msgType, **kwargs)
            if not ret:
                ret = self.onText(msgType, **kwargs)
            return ret

        def onText(self, msgType, **kwargs):
            if (msgType == _TEXT_MSGTYPE_.text):
                text = kwargs.get('text')
                #print 'Text', self, text
                if self.dispatch_event('Text', self, text):
                    return True
            elif(msgType == _TEXT_MSGTYPE_.motion):
                motion = kwargs.get('motion')
                #print 'Text Motion', self, motion
                if self.dispatch_event('Text Motion', self, motion):
                    return True
            elif(msgType == _TEXT_MSGTYPE_.select):
                motion = kwargs.get('motion')
                #print 'Text Select', self, motion
                if self.dispatch_event('Text Select', self, motion):
                    return True
            return False
        #-----------------------------------------------------------------------------
        # methods for coordinate transform
        #-----------------------------------------------------------------------------
        def window2local(self, x, y):
            path = [self]
            parent = self.parent
            while parent:
                path.append(parent)
                parent = parent.parent
            for o in path[::-1]:
                x, y = o.parent2local(x, y)
            return x, y

        def local2window(self, x, y):
            if self.parent:
                return self.parent.local2window(*self.local2parent(x, y))
            return self.local2parent(x, y)

        def global2local(self, x, y):
            path = [self]
            parent = self.parent
            while parent:
                path.append(parent)
                parent = parent.parent
            for o in path[-2::-1]:
                x, y = o.parent2local(x, y)
            return x, y

        def local2global(self, x, y):
            if self.parent:
                return self.parent.local2global(*self.local2parent(x, y))
            return x, y

        def parent2local(self, x, y):
            return x - self.left, y - self.top

        def local2parent(self, x, y):
            return x + self.left, y + self.top


        def local2screen(self, x, y):
            wx, wy = self.local2window(x, y)
            return self.window.local2screen(wx, wy)

        def screen2local(self, x, y):
            wx, wy = self.window.screen2local(x, y)
            return self.global2local(wx, wy)

        def __getGlobalRect(self):
            l, t, r, b = self.globalPosition
            return l, t, r - l, b - t
        globalRect = property(__getGlobalRect)

        def __getGlobalPosition(self):
            return self.local2global(0, 0) + self.local2global(*self.size)
        globalPosition = property(__getGlobalPosition)

        def __getWindowRect(self):
            l, t, r, b = self.windowPosition
            return l, t, r - l, b - t
        windowRect = property(__getWindowRect)

        def __getWindowPosition(self):
            return self.local2window(0, 0) + self.local2window(*self.size)
        windowPosition = property(__getWindowPosition)

        def autoDispatch(self, mapAttrEvt):
            for (attr, evt) in mapAttrEvt.items():
                 self.changeEvent(attr, self.dispatch_event, (evt,), {})

        def _postPosition(self):
            self.dispatch_event('Position Change', self)
            if self.parent:
                self.parent.dispatch_event('Child Position Change', self.parent, self)

            map(lambda c: c.dispatch_event('Parent Position Change', c, self), self.children)

        def _postSize(self):
            self.dispatch_event('Size Change', self)

            if self.parent:
                self.parent.dispatch_event('Child Size Change', self.parent, self)

            for c in self._children:
                c.dispatch_event('Parent Size Change', c, self)

        def _onEnableChanged(self):
            if self.parent:
                if self.parent.focusChild is self and not self.enabled:
                    self.parent.guessFocusChild()

            if MUIComponent.mouseOver is self:
                MUIComponent.mouseOver = None
            self.isMouseOver = False
            self.dispatch_event('Enable Change', self)

        def _onPunctureChange(self):
            self.dispatch_event('Puncture Change', self)

        def _onVisibleChange(self):
            if self.parent:
                if self.parent.focusChild is self and not self.visible:
                    self.parent.guessFocusChild()

            def __invokeVisibleChange(node):
                v = node.visible and self.visible
                node.dispatch_event('Visible Change', node)
                if node.parent:
                    node.parent.dispatch_event('Child Visible Change', node.parent, node)

            recursiveWalkComponent(self, __invokeVisibleChange)

        def setCapture(self, bCaptured):
            """
            set global cursor in captured or released state

            @param v: Capture or Release cursor
            """
            if bCaptured:
                MUIComponent.mouseCapture = self
            else:
                MUIComponent.mouseCapture = None
            if self.window:
                self.window.SetCapture(bCaptured)
                
class MUIStatic(MUIComponent):
      def __init__(self, parent=None, **kwargs):
          MUIComponent.__init__(self, parent, blind=True, **kwargs)


class MUISplitter(MUIStatic):
      def __init__(self, parent=None, **kwargs):
          super(MUISplitter, self).__init__(parent, **kwargs)


class MUIBorder(MUIStatic):
      def __init__(self, parent=None, **kwargs):
          Static.__init__(self, parent, **kwargs)
          self.border = 2

      def on_draw(self, renderer):
          if self.bgColor[0] > 0:
              renderRect = (self.left, self.top, self.right, self.bottom)
              renderer.DrawColorRectangle(renderRect, self.bgColor)



class CaptureObject:
    """ CaptureObject
    Event:
        - Capture Begin (x, y)
        - Capture End (x, y)
        - Capture Offset (x, y)
    Overwritable:
        - captureTest()
    """
    def __init__(self):
        assert isinstance(self, MUIComponent)
        self.__capture = False                  # has been captured
        self.__cap_x, self.__cap_y = 0, 0       # capture starting position

        # property
        self.useGlobalCapture = False

        self.bind('Mouse Down', self.__onCaptureMDown, postevent = False)
        self.bind('Mouse Up', self.__onCaptureMUp, postevent = False)
        self.bind('Mouse Move', self.__onCaptureMove, postevent = False)

    def captureTest(self, x, y):
        return self.hitTest(x, y)

    def __onCaptureMDown(self, comp, x, y, buttons, modifiers):
        cap = True
        self.__onCapture(cap, x, y)

    def __onCaptureMUp(self, comp, x, y, buttons, modifiers):
        cap = False
        self.__onCapture(cap, x, y)

    def __onCapture(self, cap, x, y):
        if self.__capture == cap:               # if already captured, return directly
            return
        if cap and not self.captureTest(x, y):  # not in capture region
            return

        self.__capture = cap
        #print '[CaptureObject] capture %s' %(str(self.__capture))
        self.setCapture(cap)
        if self.useGlobalCapture:
            x, y = self.local2global(x, y)
        self.__cap_x, self.__cap_y = x, y
        if self.parent:
            if self.__capture:
                self.invoke('Capture Begin', x, y)
            else:
                self.invoke('Capture End', x, y)

    def __onCaptureMove(self, comp, x, y, dx, dy):
        if self.parent and self.__capture:
            if self.useGlobalCapture:
                x, y = self.local2global(x, y)
            self.invoke('Capture Offset', x - self.__cap_x, y - self.__cap_y)

